Slick(旧Scala Query)を使ったScalaによるDBアクセス
Scala QueryとSlick
ScalaQueryとは、ScalaでRDBを扱うための低レベルなライブラリです。 RDBのデータをコレクションのように扱うことができ、シンプルで自然な記述でDBへのアクセスができるので人気があります。
今回使用するSlickとは、このScalaQueryの後継として開発されているライブラリです。 ScalaQueryを汎用化し、RDB以外にもさまざまなデータソースを扱えるようにするのことを目標に開発されています。
環境構築
今回使用した動作環境は以下のとおりです。
- OS : MacOS X 10.7.4
- sbt : 0.12
- Scala : 2.10.0 M7
- Slick : 0.11.1
なお、プロジェクトのテンプレート作成のためgiter8を使用するので、Homebrewでインストールしておいてください。 ※別にインストールしなくてもsbtのプロジェクトを手動でつくれば問題ないです
% brew install giter8
プロジェクトのセットアップ
ではまずsbtプロジェクトを作成しましょう。giter8を使用してsbtプロジェクトを作成します。 コンソールでいくつか質問されますが、とりあえず全部エンターキーを押します。
% g8 typesafehub/scala-sbt
scala-projectという名前でディレクトリが作成されたはずです。
次にsbtで依存ライブラリのインストールをしましょう。 scala-project/project/ScalaProjectBuild.scalaを下記のように編集します。 今回はDBにMySQLを使用するので依存ライブラリとしてslickとmysqlのドライバを追加します。 また、slickはScala 2.10でないと動作しないので、scalaVersionに2.10.0-M7を指定します。
import sbt._ import sbt.Keys._ object ScalaProjectBuild extends Build { lazy val scalaProject = Project( id = "scala-project", base = file("."), settings = Project.defaultSettings ++ Seq( name := "Scala Project", organization := "org.example", version := "0.1-SNAPSHOT", scalaVersion := "2.10.0-M7", //Scalaは2.10を使用 // add other settings here libraryDependencies += "com.typesafe" % "slick_2.10.0-M7" % "0.11.1", //slick libraryDependencies += "mysql" % "mysql-connector-java" % "5.1.18" % "runtime" //mysql ) ) }
sbtコンソール上で依存ライブラリをダウンロードし、リロードしましょう。
% cd scala-project % sbt > update > reload
最後にデータベースを作成しておきます。MySQLでデータベースを「mytest」という名前で作成しておきます。 これで準備ができました。
Slickを使ってみる
ではソースファイルを編集してslickを使ってみましょう。 すでにsrc/main/scala/org/example/ScalaProject.scalaファイルがあるので、このファイルを編集します。
必要なクラスのimport
まずは必要なクラスをimportしましょう。
import scala.slick.session.Database import Database.threadLocalSession import scala.slick.jdbc.{GetResult, StaticQuery => Q} import Q.interpolation import scala.slick.driver.MySQLDriver.simple._
Tableオブジェクトの作成
Appトレイトを継承して実行できるようにします。 次にTableを継承し、UserテーブルとCompanyテーブル用オブジェクトを作成します。 これらは実際のMySQLのテーブルと対応しています。(テーブルは後で作成)
object Main extends App { object User extends Table[(Int, Int,String, String, String)]("User") { def id = column[Int]("id", O.PrimaryKey) // This is the primary key column def companyId = column[Int]("companyId") def name = column[String]("name") def email = column[String]("email") def password = column[String]("password") // Every table needs a * projection with the same type as the table's type parameter def * = id ~ companyId ~ name ~ email ~ password def company = foreignKey("COMPANY_FK", companyId, Company)(_.id) } object Company extends Table[(Int,String)]("Company") { def id = column[Int]("id", O.PrimaryKey) def name = column[String]("name") def * = id ~ name }
プライマリキーや外部キーも定義可能です。 また、「」を定義しておくことで、任意のカラムを指定した状態を「<Tableオブジェクト>.」のように使うことができます。 (上記Companyの場合、idとnameのTupleとして扱える)
DBへの接続とテーブルの作成
次にDatabase.forURLで接続先を指定し、withSessionを呼びます。このブロック内でDBへの操作を行います。 このあたりはScalaQueryと同じです。
Database.forURL("jdbc:mysql://localhost/mytest", driver = "com.mysql.jdbc.Driver", user="<ユーザー名>",password="<パスワード>") withSession { try { (Company.ddl ++ User.ddl).drop } catch { case _ => println("table not found") } (Company.ddl ++ User.ddl).create
また、Tableオブジェクトの持つddlトレイトに対してcreateやdropをすることで、DBのテーブルを作成したり削除することができます。 ここではCompanyテーブルとUserテーブルがなければ削除し、テーブルを作成しています。
テストデータの登録
データの登録はinsert関数かinsertAllでおこなうことができます。
Company.insert(100,"cm") Company.insert(200,"annotation") User.insert(1, 100,"taro","taro@cm.com", "pass1") User.insert(2, 100,"mike","mike@cm.com", "pass2") User.insert(3, 100,"hanako","hanako@cm.com", "pass3") User.insertAll( (4,200,"takeshi","takeshi@cm.com","pass4"), (5,200,"jonny","jonny@cm.com","pass5") )
データの検索
ではUserを検索してみましょう。QueryにTableオブジェクトを渡してUserを全件取得しています。 また、for文とifガードを用いてSQLでテーブル同士を連結するように扱うことも可能です。
println("Users:") Query(User) foreach { case (id,companyId,name,email,pass) => println(" " + id + ":" + companyId + ":" + name + ":" + email + ":" + pass) } println("Manual join:") val q2 = for { u <- User if u.id > 1 c <- Company if c.id === u.companyId } yield (u.name, c.name) for(t <- q2) println(" " + t._1 + " working in " + t._2)
データの更新と削除
Companyの名前が「cm」のものを検索し、そのレコードの名前を「new-cm」に変更しています。 for文で返しているのがc.nameだけなので、指定するのもnameだけになっています。 その後Userテーブルでidが2のデータを削除しています。whereでidを指定してdeleteメソッドを呼んでいるだけです。
println("update:") val qu = for(c <- Company if c.name === "cm") yield c.name val updated1 = qu.update("new-cm") val q3 = for { u <- User if u.id > 1 c <- Company if c.id === u.companyId } yield (u.name, c.name) for(t <- q3) println(" " + t._1 + " working in " + t._2) println("delete:") User.where(_.id === 2).delete val q4 = for { u <- User if u.id > 1 c <- Company if c.id === u.companyId } yield (u.name, c.name) for(t <- q4) println(" " + t._1 + " working in " + t._2) } }
プログラムの実行
ではプログラムを実行してみましょう。 sbtのコンソール上でrunコマンドを実行するとプログラムが実行されます。
> run [info] Running org.example.Main ・・・ table not found Users: 1:100:taro:taro@cm.com:pass1 2:100:mike:mike@cm.com:pass2 3:100:hanako:hanako@cm.com:pass3 4:200:takeshi:takeshi@cm.com:pass4 5:200:jonny:jonny@cm.com:pass4 Manual join: mike working in cm hanako working in cm takeshi working in annotation jonny working in annotation update: mike working in new-cm hanako working in new-cm takeshi working in annotation jonny working in annotation delete: hanako working in new-cm takeshi working in annotation jonny working in annotation
なお、SQLを直接実行したい場合は下記のようにStaticQueryを使用して直接実行することもできます。
StaticQuery.queryNA[User]("select * from User") foreach { u => println(" " + u.id + "\t" + u.name + "\t") }
まとめ
いかがでしたか?コレクションを扱うように自然にRDBのアクセスができたかと思います。 最初は少しとっつきずらいところがあるかもしれませんが、関数型言語と同じく、慣れればとても使いやすいライブラリになるとおもいます。(たぶん) また、Playframework 2.2では現在使用されているAnormとSlickを統合する予定もあるようで、ますます楽しみですね。